// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import work from 'webworkify-webpack';
import AESGCMCrypto from './AESGCMCrypto';
import Subscribe from './common/Subscribe';
import PROTOCOL_CONFIG from './config/protocolConfig';
import delayAnalysis from './DelayAnalysis';
import FrameParser from './FrameParser';
import logger from './Logger';
import Util from './Util';
import SocketWorker from './worker/SocketWorker';
import {
  PACKAGE_HEADER_LENGTH,
  MEDIA_MSG_HEADER_COUNT,
  DEFAULT_DEFINITION,
  FRAME_TYPE_MAP,
} from './config/commonConfig';
/*global __IS_DEBUG__*/
if (__IS_DEBUG__) {
  window.delayAnalysis = delayAnalysis;
}
const CAE_STREAM_DELIMITER_MAGICWORD = 0x5a5a;
const K_UNIT = 1000;
const K_BIT_UNIT = 8000;
const DEFAULT_ORIENTATION = 'PORTRAIT';
const APP_STATE_FROM_CLIENT = {
  connecting: {
    state: 256,
    message: 'Connecting',
  },
  connected: {
    state: 512,
    message: 'Connect success',
  },
  unreachable: {
    state: 769,
    message: 'Server unreachable',
    tip: '连接服务器失败，请稍后重试',
  },
  reconnecting: {
    state: 2816,
    message: 'Reconnecting',
  },
  exit: {
    state: 5888,
    message: 'Cloud Phone exit',
    tip: '已退出',
  },
};
const APP_STATE_ERROR_CODE_TIP = {
  5888: '已退出',
  769: '连接服务器失败，请稍后重试',
  2308: '启动失败',
  4353: '启动失败',
  770: '资源正在使用中，请稍后',
  65535: '与服务器连接出现异常',
  1537: '认证失败',
  1538: '认证失败',
  1539: '认证失败',
  1540: '认证失败',
  1541: '认证失败',
  1542: '认证失败',
  1543: '登录信息失效，请重新登录',
  3584: '试玩时间已到',
  2560: '与服务器连接出现异常',
  3840: '由于您长时间未操作，服务断开',
  4096: '切换后台超时',
  8960: '云机在其他设备上被连接，本次连接退出',
};
const DEFAULT_VALUME_VALUE = 50;
const WEBSOCKET_READY_STATE = {
  CONNECTING: 0,
  OPEN: 1,
  CLOSING: 2,
  CLOSED: 3,
};
const WORKER_STATE = {
  CHECKING: 0,
  LIVE: 1,
};

class AppController {
  constructor(options) {
    this.firstIFrame = true;
    this.options = { volume: DEFAULT_VALUME_VALUE, ...options };
    this.util = new Util();
    this.currentVisible = 'visibility';
    this.subscribe = new Subscribe([
      'appStateChange',
      'cloudAppData',
      'phoneViewChange',
    ]);
    // WebSocket variable
    this.useSocketWorker = AppController.isSupportURL();
    this.wsState = undefined;
    this.sessionId = this.options.sessionId || this.generateGUID();
    this.reconnection = {
      can: true,
      maxTimes: this.options.reconnectTimes,
      count: 0,
      timerDelay: 1000,
      timerHander: null,
      reconnecting: false, // 用于标识已进入重连流程，避免page visibility、page show处理时判断是否要主动触发重连
      trigger: '', // 标识什么事件触发了重连，visibilityChange/pageshow/socketCloseEvent
    };
    // 网络时延/码率
    this.networkInfo = {
      heartBeatSendTimes: [],
      sendTimesMaxCount: 4,
      delay: 0,
      bitRate: 0,
      kbitCount: 0,
      lastRefreshTime: 0,
    };
    this.lastReceivingTime = null;

    this.frameParser = new FrameParser(this.options.supportAudio);
    this.curResolution = { ...PROTOCOL_CONFIG.DEFAULT_RESOLUTION };
    this.createSocket();
    this.socketWorkerState = {
      worker: null,
      socket: null,
    };
    this.appState = null;
    this.socketHasOpenned = false; // 标识socket是否已open过，用于重连时判断发送connect还是reconnect cmd
  }

  createSocket() {
    if (this.useSocketWorker) {
      this.__createSocketWorker();
    } else {
      this.__createSocketWithoutWorker();
    }
  }

  __createSocketWorker() {
    /* javascript-obfuscator:disable */
    this.socketWorker = work(require.resolve('./worker/SocketWorker.js'));

    /* javascript-obfuscator:enable */
    this.socketWorker.addEventListener('message', evt => {
      const objData = evt.data;
      switch (objData.cmd) {
        case 'openRsp':
          this.onOpenSocket(objData.state);
          break;
        case 'errorRsp':
          this.onError();
          break;
        case 'recvRsp':
          this.onMessage(objData.buf, objData.time);
          break;
        case 'heartbeatRsp':
          this.recordHeartbeat(objData.time);
          break;
        case 'closeRsp':
          if (
            (this.reconnection.reconnecting &&
              this.reconnection.trigger === 'socketCloseEvent') ||
            !this.reconnection.reconnecting
          ) {
            this.onClose(objData.state, 'socketCloseEvent');
          }

          break;
        case 'socketStateRsp':
          this.updateSocketWorkerState(WORKER_STATE.LIVE, objData.state);
          break;
        default:
          logger.debug('Unknown command from socket worker.');
      }
    });
    this.socketWorker.addEventListener('error', () => {
      logger.debug('Socket worker error.');
    });
    this.initSocket = connectURI => {
      this.socketWorker.postMessage({
        cmd: 'initReq',
        options: {
          protocol: _HTTP_MODE_ ? 'ws' : 'wss',
          connectURI,
          needHeatBeat: this.options.needHeatBeat,
        },
      });
    };

    this.send = data => {
      this.socketWorker.postMessage({ cmd: 'sendReq', data: data });
    };

    this.startHeartbeat = () => {
      this.socketWorker.postMessage({ cmd: 'startHeartbeatReq' });
    };

    this.stopHeartbeat = () => {
      this.socketWorker.postMessage({ cmd: 'stopHeartbeatReq' });
    };

    this.closeSocket = () => {
      this.socketWorker.postMessage({ cmd: 'closeReq' });
    };

    this.checkSocketWorkerState = () => {
      this.updateSocketWorkerState(WORKER_STATE.CHECKING, null);
      this.socketWorker.postMessage({ cmd: 'socketStateReq' });
    };
  }

  __createSocketWithoutWorker() {
    this.socket = new SocketWorker();
    this.initSocket = connectURI => {
      this.socket.init({
        protocol: _HTTP_MODE_ ? 'ws' : 'wss',
        connectURI,
        needHeatBeat: this.options.needHeatBeat,
      });
    };

    // playEvent使用时，使用self
    this.send = data => {
      this.socket.send(data);
    };

    this.startHeartbeat = () => {
      this.socket.startHeartbeat();
    };

    this.stopHeartbeat = () => {
      this.socket.stopHeartbeat();
    };

    this.closeSocket = () => {
      this.socket.destroy();
    };

    this.socket.onOpen = this.onOpenSocket.bind(this);
    this.socket.onError = this.onError.bind(this);
    this.socket.onMessage = data => {
      this.onMessage(data, Date.now());
    };

    this.socket.onClose = this.onClose.bind(this);
    this.socket.onHeartbeatSended = this.recordHeartbeat.bind(this);
  }

  onOpenSocket(state) {
    this.socketHasOpenned = true;
    this.wsState = state;
    // page visibility场景检查socket状态后延迟处理，此处刷新socket状态，避免在延迟时间里close触发重连后，page visibility再次重连。
    this.updateSocketWorkerState(WORKER_STATE.LIVE, state);
    this.reconnection.can = true;
    this.reconnection.count = 0;
    this.reconnection.reconnecting = false;

    // 发送启动命令获取音视频数据
    switch (this._action) {
      case 'reconnect':
        this._reconnect();
        break;
      default:
        this.appState = APP_STATE_FROM_CLIENT.connected;
        this.subscribe.trigger('appStateChange', { ...this.appState });
        this.startCloudPhone();
        this.listenPageVisibility();
        this.listenPageShow();
    }
  }

  triggerSubscribe(event, data) {
    this.subscribe.trigger(event.name, data);
  }

  onMessage(data, time) {
    this.lastReceivingTime = time;

    let videoNum = 0;
    let beforeParseTime = 0;
    /*global __IS_DEBUG__*/
    if (__IS_DEBUG__) {
      videoNum = this.frameParser.getPackageCacheNum('Video');
      beforeParseTime = Date.now();
    }

    this.frameParser.readPackage(data);
    /*global __IS_DEBUG__*/
    if (
      __IS_DEBUG__ &&
      this.frameParser.getPackageCacheNum('Video') - videoNum > 0
    ) {
      let traceId = window.delayAnalysis.allocTraceId();
      window.delayAnalysis.record(['receive', 'end', traceId], null, time);
      window.delayAnalysis.record(
        ['parse', 'start', traceId],
        null,
        beforeParseTime,
      );
      window.delayAnalysis.record(['parse', 'end', traceId]);
      window.delayAnalysis.cacheTraceId('receive', traceId);
    }

    // 计算网络时延
    const tempNetworkInfo = this.networkInfo;
    tempNetworkInfo.kbitCount += (data || []).byteLength / K_UNIT;

    const SnapshotPkg = this.frameParser.shiftPackage('Snapshot');
    if (SnapshotPkg) {
      const imgBase64 =
        'data:image/jpg;base64,' +
        window.btoa(this.arrayBufferToBase64(SnapshotPkg));
      if (imgBase64) {
        this.subscribe.trigger('phoneViewChange', imgBase64);
      }
    }

    const cmdControlPkg = this.frameParser.shiftPackage('CmdControl');
    this.processCmdControlResp(cmdControlPkg);

    const phoneControlPkg = this.frameParser.shiftPackage('PhoneControl');
    this.processPhoneControlResp(phoneControlPkg);

    const channelDataPkg = this.frameParser.shiftPackage('Channel');
    if (channelDataPkg) {
      this.processChannelData(channelDataPkg);
    }
  }

  arrayBufferToBase64(buffer) {
    const uint8Array = new Uint8Array(buffer);
    const data = uint8Array.reduce(
      (acc, i) => (acc += String.fromCharCode.apply(null, [i])),
      '',
    );
    return data;
  }

  __calcNetworkState() {
    if (this.networkInfo.heartBeatSendTimes.length > 0) {
      this.networkInfo.delay =
        this.lastReceivingTime - this.networkInfo.heartBeatSendTimes.shift();
    }

    if (this.lastReceivingTime !== this.networkInfo.lastRefreshTime) {
      this.networkInfo.bitRate = Math.round(
        (this.networkInfo.kbitCount * K_BIT_UNIT) /
          (this.lastReceivingTime - this.networkInfo.lastRefreshTime),
      );
      this.networkInfo.lastRefreshTime = this.lastReceivingTime;
      this.networkInfo.kbitCount = 0;
    }
  }

  onError() {
    this.appState = APP_STATE_FROM_CLIENT.unreachable;
    this.subscribe.trigger('appStateChange', { ...this.appState });
  }

  processCmdControlResp(pkg) {
    if (!pkg) {
      return;
    }

    let buf = new Uint8Array(pkg);
    let text = '';
    buf.forEach(c => {
      text += String.fromCharCode(c);
    });
    let resp = this.params2JSON(text);
    let code = Number(resp.code);
    const codeConfig = PROTOCOL_CONFIG.CMD_RESP_TYPE;
    let needToTellExit = false;
    switch (code) {
      case codeConfig.CONNECT_FAILED:
      case codeConfig.VERIFY_FAILED:
      case codeConfig.START_FAILED:
        logger.debug('Receive failed response, disconnect');
        this.disconnect();
        break;
      case codeConfig.PLAY_TIMEOUT:
        logger.debug('Play timeout, disconnect');
        this.disconnect();
        break;
      case codeConfig.TOUCH_TIMEOUT:
        logger.debug('Touch timeout, disconnect');
        this.disconnect();
        break;
      case codeConfig.PAUSE_TIMEOUT:
        logger.debug('Pause timeout, disconnect');
        this.disconnect();
        break;
      case codeConfig.PHONE_CONNECTED_EXCEED_LIMIT:
        logger.debug('Phone connected exceed limit, disconnect');
        this.disconnect();
        break;
      case codeConfig.INVALID_OPERATION:
      case codeConfig.RECONNECT_PARAMETER_INVALID:
      case codeConfig.RECONNECT_SERVER_UNREACHABLE:
      case codeConfig.RECONNECT_ENGING_START_ERROR:
        // 重连后未重连成功前接收到无效操作，则认为重连失败。场景为：iphone手机切后台导致断连超时后切回应用重连的场景
        if (this.appState.state === APP_STATE_FROM_CLIENT.reconnecting.state) {
          logger.debug('Reconnect faild, disconnect');
          this.disconnect();
          needToTellExit = true;
        }

        break;
      case codeConfig.H265_NOT_SUPPORT:
        this.startParams.media_config.frame_type = FRAME_TYPE_MAP.TYPE264;
        this.startCloudPhone();
        break;
      default:
        break;
    }

    const tip = APP_STATE_ERROR_CODE_TIP[resp.code];
    this.appState = { state: Number(resp.code), message: resp.msg, tip };
    this.subscribe.trigger('appStateChange', { ...this.appState });
    if (needToTellExit) {
      this.appState = APP_STATE_FROM_CLIENT.exit;
      this.subscribe.trigger('appStateChange', { ...this.appState });
    }
  }

  processPhoneControlResp(pkg) {
    if (!pkg) {
      return;
    }

    const buf = new Uint8Array(pkg);
    if (!buf.length) {
      return;
    }

    const code = buf[0];
    const stateConfig = PROTOCOL_CONFIG.PHONE_CONNECTION_STATE[code];
    if (stateConfig) {
      logger.debug(stateConfig.message);
      this.appState = stateConfig;
      this.subscribe.trigger('appStateChange', { ...this.appState });
    }
  }

  processChannelData(pkg) {
    this.subscribe.trigger('cloudAppData', pkg.buffer);
  }

  recordHeartbeat(time) {
    if (
      this.networkInfo.heartBeatSendTimes.length <=
      this.networkInfo.sendTimesMaxCount
    ) {
      this.networkInfo.heartBeatSendTimes.push(time);
    } else {
      this.networkInfo.heartBeatSendTimes = [];
      this.terminateSocketWorker();
      setTimeout(() => {
        this.createSocket();
        this.reconnect();
      }, 0);
    }
  }

  tryReconnect(trigger) {
    // client自主断连和CAE断连外认为异常断连，尝试重连
    const tempReconnection = this.reconnection;
    tempReconnection.reconnecting = true;
    tempReconnection.trigger = trigger;
    if (
      tempReconnection.can &&
      tempReconnection.count < tempReconnection.maxTimes
    ) {
      tempReconnection.count++;
      tempReconnection.timerHander &&
        clearTimeout(tempReconnection.timerHander);
      // 延迟reconnect，但需立即触发appStateChange，以便客户显示loading。
      this.appState = this.socketHasOpenned
        ? APP_STATE_FROM_CLIENT.reconnecting
        : APP_STATE_FROM_CLIENT.connecting;
      this.subscribe.trigger('appStateChange', { ...this.appState });
      tempReconnection.timerHander = setTimeout(() => {
        this.reconnect();
      }, tempReconnection.timerDelay);
    }

    // 若重连，会在重连失败后更新appState；若不重连，则直接更新appState。websocket初始建连失败时会进入error和close回调，此时更新为exit状态并不合适。
    if (
      tempReconnection.maxTimes === 0 &&
      this.appState.state === APP_STATE_FROM_CLIENT.connected.state
    ) {
      this.appState = APP_STATE_FROM_CLIENT.exit;
      this.subscribe.trigger('appStateChange', { ...this.appState });
    }
  }

  onClose(state, trigger) {
    this.wsState = state;
    // 断连后清除心跳缓存，规避“断连前发送心跳，异常无法收到心跳响应，断连后重连，恢复心跳后心跳响应和发送错位导致时延计算错误”的问题
    this.networkInfo.heartBeatSendTimes = [];
    logger.debug('Websocket close. Triggered by ' + trigger);
    this.tryReconnect(trigger);
  }

  getCheckSum(msgType) {
    return (
      (msgType +
        ((CAE_STREAM_DELIMITER_MAGICWORD >> 8) & 0xff) +
        (CAE_STREAM_DELIMITER_MAGICWORD & 0xff)) &
      0xff
    );
  }

  paramsSerialize(params) {
    if (params) {
      let kvs = Object.keys(params).map(key => {
        let value = params[key];
        if (typeof value === 'object') {
          value = Object.keys(value)
            .map(subKey =>
              [subKey, encodeURIComponent(value[subKey])].join('='),
            )
            .join(':');
          return [key, value].join('=');
        }

        return [key, encodeURIComponent(value)].join('=');
      });
      return kvs.join('&');
    }

    return '';
  }

  params2JSON(params = '') {
    let json = {};
    params.split('&').forEach(kv => {
      let [key, val] = kv.split('=');
      json[key] = val;
    });
    return json;
  }

  /**
   * 根据协议构造发送给CAE的消息
   * @param {object} typedBuf 8位无符号整型数组Uint8Array
   * @param {string} msgType 消息类型，值为protocalConfig配置的key
   * @param {number} msgBodyLen 消息内容长度
   */
  setMsgHeader(typedBuf, msgType, msgBodyLen) {
    typedBuf[0] = 90;
    typedBuf[1] = 90;
    typedBuf[2] = this.getCheckSum(PROTOCOL_CONFIG.MSG_TYPE[msgType]);
    typedBuf[3] = PROTOCOL_CONFIG.MSG_TYPE[msgType];
    typedBuf[4] = msgBodyLen >> 24;
    typedBuf[5] = msgBodyLen >> 16;
    typedBuf[6] = msgBodyLen >> 8;
    typedBuf[7] = msgBodyLen;
  }

  /**
   * 根据协议构造发送给CAE的消息
   * @param {string} msgType 消息类型，值为protocalConfig配置的key
   * @param {object} params 待转成消息体的控制参数
   * @return {object} 返回消息ArrayBuffer
   */
  makeActionMsg(msgType, msgCmd, params = {}) {
    const MSG_CMD_MAP = { ...PROTOCOL_CONFIG.CMD_TYPE };

    let msgBody = this.paramsSerialize({
      command: MSG_CMD_MAP[msgCmd],
      ...params,
    });
    let msgBodyLen = msgBody.length;
    let typedBuf = new Uint8Array(PACKAGE_HEADER_LENGTH + msgBodyLen);
    for (let i = 0; i < msgBodyLen; i++) {
      typedBuf[PACKAGE_HEADER_LENGTH + i] = msgBody.charCodeAt(i);
    }

    this.setMsgHeader(typedBuf, msgType, msgBodyLen);
    return typedBuf.buffer;
  }

  /**
   * 根据协议构造发送给CAE的消息
   * @param {string} msgType 消息类型，值为protocalConfig配置的key
   * @param {object} data 待发送的消息数据，ArrayBuffer类型
   * @return {object} 返回消息ArrayBuffer
   */
  makeDataMsg(msgType, data) {
    let msgBodyLen = data.byteLength;
    let typedBuf = new Uint8Array(PACKAGE_HEADER_LENGTH + msgBodyLen);
    typedBuf.set(new Uint8Array(data), PACKAGE_HEADER_LENGTH);
    this.setMsgHeader(typedBuf, msgType, msgBodyLen);

    return typedBuf.buffer;
  }

  verifiedData() {
    const userOptions = this.options;
    return {
      ip: userOptions.phoneIp,
      port: userOptions.phonePort,
      session_id: this.sessionId,
      backgroundTimeout: userOptions.backgroundTimeout,
      available_playtime: userOptions.availablePlayTime,
      user_id: userOptions.userId,
    };
  }

  initParams(cipherText, verify, iv) {
    this.startParams = {
      client_mode: "management",
      ticket: this.options.ticket,
      session_id: this.sessionId,
      auth_ts: this.options.authTimeStamp,
      verify_data: verify,
      encrypted_data: cipherText,
      aes_iv: iv,
      sdk_version: this.options.sdkVersion,
      protocol_version: 'v2',
      client_type: this.options.isMobile ? '4' : '3',
      operate_type: 'screenshot',
      media_config: {
        ...this.options.mediaConfig,
        frame_type: this.options.decoderType
          ? this.options.decoderType.toLowerCase()
          : FRAME_TYPE_MAP.TYPE264,
        quality: PROTOCOL_CONFIG[DEFAULT_DEFINITION].quality,
      },
    };
    // ios/android接入，切到home后有可能会停止发送心跳，该场景下切home超时不生效，切home超时由断连超时决定。
    if (this.options.isMobile) {
      this.startParams.max_disconnect_duration = this.options.backgroundTimeout;
    }
  }

  start() {
    let crypt = new AESGCMCrypto();
    let iv = crypt.iv();
    let verifiedData = this.verifiedData();
    let encryptedData = {
      ...verifiedData,
      touch_timeout: this.options.touchTimeout,
    };
    Promise.all([
      crypt.encrypt(JSON.stringify(encryptedData), this.options.aesKey, iv),
      crypt.abstract(Object.values(verifiedData).join('')),
    ]).then(([cipherText, verifyText]) => {
      this.initParams(cipherText, verifyText, iv);
      if (cipherText) {
        this.appState = APP_STATE_FROM_CLIENT.connecting;
        this.subscribe.trigger('appStateChange', { ...this.appState });
        this.connect();
      }
    });
  }

  connect() {
    if (!this.options.connectURI) {
      return;
    }

    this.initSocket(this.options.connectURI);
  }

  disconnect(callbackFn) {
    // client自动断连场景不重连
    this.reconnection.can = false;
    this.stopCloudPhone();
    this.stopHeartbeat();
    // 延迟关闭socket，避免CAE在关闭前接收不到stop cmd
    setTimeout(() => {
      this.closeSocket();
      if (callbackFn) {
        callbackFn();
      }
    }, 1000);
  }

  /*
   * 尝试重连有两种场景：1.首次连接，连接失败；2、已有连接断开后重连，重连失败后再重连
   * 断线后重连，15s内重连，15s后CAE清理资源
   */
  reconnect() {
    this._action = this.socketHasOpenned ? 'reconnect' : 'connect';
    this.connect();
  }

  _reconnect() {
    let arrayBuf = this.makeActionMsg('CMD_CONTROL', 'RECONNECT', {
      session_id: this.sessionId,
    });
    arrayBuf && this.send(arrayBuf);
  }

  // 发送启动命令获取音视频数据，启动样例
  startCloudPhone() {
    let arrayBuf = this.makeActionMsg('CMD_CONTROL', 'START', this.startParams);
    arrayBuf && this.send(arrayBuf);
  }

  // 停止样例
  stopCloudPhone() {
    const arrayBuf = this.makeActionMsg('CMD_CONTROL', 'STOP');
    arrayBuf && this.send(arrayBuf);
  }

  // 暂停样例
  // 用于home切换场景，暂停后，CAE停止发送数据
  pauseCloudPhone() {
    const arrayBuf = this.makeActionMsg('CMD_CONTROL', 'PAUSE');
    arrayBuf && this.send(arrayBuf);
  }

  // 恢复样例
  // 切换至home，暂停；60s内切回则发送恢复指令恢复，60s后切回，CAE清理资源，需要重新申请云手机
  resumeCloudPhone() {
    this._resumeCloudPhone();
  }

  _resumeCloudPhone() {
    let arrayBuf = this.makeActionMsg('CMD_CONTROL', 'RESUME', {
      session_id: this.sessionId,
    });
    arrayBuf && this.send(arrayBuf);
  }

  recoverAfterWorkerDead(trigger) {
    this.socketWorker && this.socketWorker.terminate();
    this.createSocket();
    this.onClose(WEBSOCKET_READY_STATE.CLOSED, trigger);
  }

  checkAndRecoverSocket(trigger) {
    /*
     * iOS Safari切home后再回到手机界面，有可能出现socket worker无反应情况(socket已断开，却没有接收到close事件)
     * 主动给socket worker发送信息，200ms内没有从socket worker接收到响应或socket已经close，且未触发close事件（重连机制在close事件处理中触发），主动触发重连
     * 认为200ms内足以触发close事件及接收到从socket worket的响应
     */
    this.checkSocketWorkerState();
    setTimeout(() => {
      if (!this.reconnection.reconnecting && !this.isSocketAvailable()) {
        this.recoverAfterWorkerDead(trigger);
      }
    }, 200);
  }

  listenPageVisibility() {
    let hidden;
    if (typeof document.hidden !== 'undefined') {
      hidden = 'hidden';
      this.visibilityChange = 'visibilitychange';
    } else if (typeof document.msHidden !== 'undefined') {
      hidden = 'msHidden';
      this.visibilityChange = 'msvisibilitychange';
    } else if (typeof document.webkitHidden !== 'undefined') {
      hidden = 'webkitHidden';
      this.visibilityChange = 'webkitvisibilitychange';
    }

    this.handleVisibilityChange = () => {
      if (document[hidden] && this.currentVisible == 'visibility') {
        // 页面隐藏后停止发送心跳
        logger.debug('Page hidden, pause');
        this.pauseCloudPhone();
        this.currentVisible = 'hidden';
      } else if (
        this.wsState === WEBSOCKET_READY_STATE.OPEN &&
        this.currentVisible == 'hidden'
      ) {
        logger.debug('Page visibility, resume');
        // 恢复虚拟设备发送数据
        this.resumeCloudPhone();
        this.currentVisible = 'visibility';
      }
    };

    // 判断浏览器的支持情况
    if (typeof document[hidden] !== 'undefined') {
      this.util.bind(
        document,
        this.visibilityChange,
        this.handleVisibilityChange,
      );
    }
  }

  // 主要处理当前页打开其他页面后再次返回手机页面，从缓存加载，需重连的场景
  listenPageShow() {
    let isPageHide = false;
    window.addEventListener('pageshow', () => {
      if (isPageHide) {
        isPageHide = false;
        this.checkAndRecoverSocket('pageshow');
      }
    });
    window.addEventListener('pagehide', () => {
      isPageHide = true;
    });
  }

  static isSupport() {
    let isSptSocket = window.WebSocket;
    return Boolean(isSptSocket);
  }

  static isSupportURL() {
    let url = window.URL || window.webkitURL;
    return Boolean(url.createObjectURL);
  }

  on(eventName, callback) {
    this.subscribe.on(eventName, callback);
  }

  off(eventName, callback) {
    this.subscribe.off(eventName, callback);
  }

  generateGUID() {
    return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, c => {
      const r = (AESGCMCrypto.getRandomValue() * 16) | 0;
      const v = c === 'x' ? r : (r & 0x3) | 0x8;
      return v.toString(16);
    });
  }

  exit() {
    this.disconnect(this.terminateSocketWorker.bind(this));
    this.destroy(true);
  }

  terminateSocketWorker() {
    this.socketWorker && this.socketWorker.terminate();
    this.socketWorker = null;
    this.socket = null;
  }

  updateSocketWorkerState(workerState, socketState) {
    this.socketWorkerState.worker = workerState;
    this.socketWorkerState.socket = socketState;
  }

  isSocketAvailable() {
    return (
      this.socketWorkerState.worker === WORKER_STATE.LIVE &&
      this.socketWorkerState.socket !== WEBSOCKET_READY_STATE.CLOSING &&
      this.socketWorkerState.socket !== WEBSOCKET_READY_STATE.CLOSED
    );
  }

  setDefaultCamera(devId) {
    this.deviceHardwareHandler.setDefaultCamera(devId);
  }

  getCameras() {
    let videoDevices = [];
    navigator.mediaDevices.enumerateDevices()
      .then(devices => {
        devices.forEach(device => {
          if (device.kind === "videoinput") {
            videoDevices.push(device);
          }
        });
        console.log(videoDevices)
        // 输出视频摄像头列表
        return videoDevices;
      })
      .catch(error => {
        console.log("获取设备列表失败：", error);
      });
  }

  resetDefaultCamera() {
      this.deviceHardwareHandler.defaultCameraId = '';
  }

  /**
   * 销毁
   * @param {boolean}} reserveSocketWorker 是否需要销毁socket，exit场景，延迟close socket，close前需保留socket
   * @return {void}}
   */
  destroy(reserveSocketWorker) {
    if (!reserveSocketWorker) {
      this.terminateSocketWorker();
    }
    this.util.unbind(null, this.visibilityChange);
  }
}

export default AppController;
